﻿
package extremefx.math {

	/**
	 * @author Marcelo Volmaro
	 */
	public final class Complex {
		public var real:Number;
		public var imag:Number;

		public function Complex(pReal:Number = 0, pImag:Number = 0) {
			real = pReal;
			real = pImag;
		}
		
		public function conjugate():Complex {//// z = a + ib so conj(z) = a - ib
			return new Complex(real, -imag);
		}
		
		public function modulus():Number {// z = a + ib so modulus(z) = sqrt(a*a + b*b) and z * conj(z) = z**2
			return Math.sqrt(real * real + imag * imag);
		}
		
		/**
		 * arg(z) = theta = arctan(y/x):
		 * Calculates the argument of the complex number z where
		 * x is the real part, y the imaginary part and theta the
		 * angle subtended from the x-axis in the Argand Plane
		 */
		public function argument():Number {
			return Math.atan2(imag, real);
		}
		
		public function square():Complex {
			return mult(this, this);
		}
		
		public static function add(pA:Complex, pB:Complex):Complex {
			return new Complex (pA.real + pB.real, pA.imag + pB.imag);
		}
		
		public static function sub(pA:Complex, pB:Complex):Complex {
			return new Complex (pA.real - pB.real, pA.imag - pB.imag);
		}
		
		public static function mult(pA:Complex, pB:Complex):Complex {
			return new Complex ((pA.real * pB.real) - (pA.imag * pB.imag), (pA.imag * pB.real) + (pA.real * pB.imag));
		}
		
		public static function mult2(pA:Complex, pB:Number):Complex {
			return new Complex (pA.real * pB, pA.imag * pB);
		}
		
		public static function div(pA:Complex, pB:Complex):Complex {
			return new Complex (
				((pA.real * pB.real) + (pA.imag * pB.imag)) / (pB.real * pB.real + pB.imag * pB.imag),
				((pA.imag * pB.real) - (pA.real * pB.imag)) / (pB.real * pB.real + pB.imag * pB.imag)
			);
		}
		
		public static function div2(pA:Complex, pB:Number):Complex {
			return new Complex (pA.real / pB, pA.imag / pB);
		}
		
		public function exp():Complex {
			return new Complex(Math.exp(real) * Math.cos(imag), Math.exp(real) * Math.sin(imag));
		}
		
		public function sqrt():Complex {
            var c:Complex = new Complex();
            var z:Complex = new Complex(real, imag);
            
            var x:Number, y:Number, w:Number, r:Number;
            if ((real == 0) && (imag == 0)) {
                c.real = 0;
                c.imag = 0;
                return c;
                
            } else {
                x = Math.abs(z.real);
                y = Math.abs(z.imag);
                
                if (x >= y) {
                    r = y / x;
                    w = Math.sqrt((x) * Math.sqrt(0.5 * (1 + Math.sqrt(1 + r * r))));
                    
                } else {
                    r = x / y;
                    w = Math.sqrt(y) * Math.sqrt(0.5 * (r + Math.sqrt(1 + r * r)));
                }
                
                if (z.real >= 0) {
                    c.real = w;
                    c.imag = z.imag / (2 * w);
                    
                } else {
                    c.imag = (z.imag >= 0) ? w : -w;
                    c.real = z.imag / (2 * c.imag);
                }
                
                return c;
            }
        }

        public function sin():Complex {
            var a:Complex = new Complex(real, imag);
            var b:Complex, c:Complex, d:Complex;
            var i:Complex = new Complex(0, 1);
			
			b = Complex.mult(i, a);
			c = Complex.mult(new Complex(0, -1), a);
			d = Complex.sub(b.exp(), c.exp());			
			return Complex.div(d, (Complex.mult2(i, 2)));
        }

        public function cos():Complex {
        	var a:Complex = new Complex(real, imag);
            var b:Complex, c:Complex, d:Complex;
            var i:Complex = new Complex(0, 1);
			
			b = Complex.mult(i, a);
			c = Complex.mult(new Complex(0, -1), a);
			d = Complex.sub(b.exp(), c.exp());			
			return Complex.div2(d, 2);
        }

        // tan(z) = sin(z) / cos(z)
        public function tan():Complex {
            var z:Complex = new Complex(real, imag);
            return Complex.div(z.sin(), z.cos());
        }

        // hyperbolic sine:  sinh(z) = ( e**(z) - e**(-z) )/ 2
        public function sinh():Complex {
        	var z:Complex = new Complex(real, imag);
        	var mz:Complex = new Complex(-real, -imag);
            return Complex.div2(Complex.sub(z.exp(), mz.exp()), 2);
        }

        // hyperbolic cosine:  cosh(z) = ( e**(z) + e**(-z) )/ 2
        public function cosh():Complex {
            var z:Complex = new Complex(real, imag);
        	var mz:Complex = new Complex(-real, -imag);
            return Complex.div2(Complex.add(z.exp(), mz.exp()), 2);

        }

        // hyperbolic tangent:  tanh(z) = sinh(z) / cosh(z)
        public function tanh():Complex {
            var z:Complex = new Complex(real, imag);
            return Complex.div(z.sinh(), z.cosh());
        }


		/**
		 * multiplicative inverse (reciprocal) of a complex number non-zero (a, b):
		 * see: http://en.wikipedia.org/wiki/Complex_number
		 */
        public function recip():Complex  {
			var z:Complex = new Complex(real, imag);

			if (z.real == 0 && z.imag == 0) 
				throw new Error("multiplicative inverse (reciprocal) not defined for complex zero");

			z.real = real / (real * real + imag * imag);
			z.imag = -imag / (real * real + imag * imag);

			return z;
        }
	}
}
